@@ -9,8 +9,7 @@ class AgentsController < ApplicationController |
||
9 | 9 |
end |
10 | 10 |
|
11 | 11 |
def run |
12 |
- @agent = current_user.agents.find(params[:id]) |
|
13 |
- @agent.async_check |
|
12 |
+ Agent.async_check(current_user.agents.find(params[:id]).id) |
|
14 | 13 |
redirect_to agents_path, notice: "Agent run queued" |
15 | 14 |
end |
16 | 15 |
|
@@ -100,20 +100,6 @@ class Agent < ActiveRecord::Base |
||
100 | 100 |
@memoized_last_event_at ||= events.select(:created_at).first.try(:created_at) |
101 | 101 |
end |
102 | 102 |
|
103 |
- def async_check |
|
104 |
- check |
|
105 |
- self.last_check_at = Time.now |
|
106 |
- save! |
|
107 |
- end |
|
108 |
- handle_asynchronously :async_check #, :priority => 10, :run_at => Proc.new { 5.minutes.from_now } |
|
109 |
- |
|
110 |
- def async_receive(event_ids) |
|
111 |
- receive(Event.where(:id => event_ids)) |
|
112 |
- self.last_receive_at = Time.now |
|
113 |
- save! |
|
114 |
- end |
|
115 |
- handle_asynchronously :async_receive #, :priority => 10, :run_at => Proc.new { 5.minutes.from_now } |
|
116 |
- |
|
117 | 103 |
def default_schedule |
118 | 104 |
self.class.default_schedule |
119 | 105 |
end |
@@ -135,67 +121,93 @@ class Agent < ActiveRecord::Base |
||
135 | 121 |
end |
136 | 122 |
|
137 | 123 |
# Class Methods |
124 |
+ class << self |
|
125 |
+ def cannot_be_scheduled! |
|
126 |
+ @cannot_be_scheduled = true |
|
127 |
+ end |
|
138 | 128 |
|
139 |
- def self.cannot_be_scheduled! |
|
140 |
- @cannot_be_scheduled = true |
|
141 |
- end |
|
142 |
- |
|
143 |
- def self.cannot_be_scheduled? |
|
144 |
- !!@cannot_be_scheduled |
|
145 |
- end |
|
146 |
- |
|
147 |
- def self.default_schedule(schedule = nil) |
|
148 |
- @default_schedule = schedule unless schedule.nil? |
|
149 |
- @default_schedule |
|
150 |
- end |
|
151 |
- |
|
152 |
- def self.cannot_receive_events! |
|
153 |
- @cannot_receive_events = true |
|
154 |
- end |
|
129 |
+ def cannot_be_scheduled? |
|
130 |
+ !!@cannot_be_scheduled |
|
131 |
+ end |
|
155 | 132 |
|
156 |
- def self.cannot_receive_events? |
|
157 |
- !!@cannot_receive_events |
|
158 |
- end |
|
133 |
+ def default_schedule(schedule = nil) |
|
134 |
+ @default_schedule = schedule unless schedule.nil? |
|
135 |
+ @default_schedule |
|
136 |
+ end |
|
159 | 137 |
|
160 |
- def self.receive! |
|
161 |
- sql = Agent. |
|
162 |
- select("agents.id AS receiver_agent_id, sources.id AS source_agent_id, events.id AS event_id"). |
|
163 |
- joins("JOIN links ON (links.receiver_id = agents.id)"). |
|
164 |
- joins("JOIN agents AS sources ON (links.source_id = sources.id)"). |
|
165 |
- joins("JOIN events ON (events.agent_id = sources.id)"). |
|
166 |
- where("agents.last_checked_event_id IS NULL OR events.id > agents.last_checked_event_id").to_sql |
|
138 |
+ def cannot_receive_events! |
|
139 |
+ @cannot_receive_events = true |
|
140 |
+ end |
|
167 | 141 |
|
168 |
- agents_to_events = {} |
|
169 |
- Agent.connection.select_rows(sql).each do |receiver_agent_id, source_agent_id, event_id| |
|
170 |
- agents_to_events[receiver_agent_id] ||= [] |
|
171 |
- agents_to_events[receiver_agent_id] << event_id |
|
142 |
+ def cannot_receive_events? |
|
143 |
+ !!@cannot_receive_events |
|
172 | 144 |
end |
173 | 145 |
|
174 |
- event_ids = agents_to_events.values.flatten.uniq.compact |
|
146 |
+ def receive! |
|
147 |
+ sql = Agent. |
|
148 |
+ select("agents.id AS receiver_agent_id, sources.id AS source_agent_id, events.id AS event_id"). |
|
149 |
+ joins("JOIN links ON (links.receiver_id = agents.id)"). |
|
150 |
+ joins("JOIN agents AS sources ON (links.source_id = sources.id)"). |
|
151 |
+ joins("JOIN events ON (events.agent_id = sources.id)"). |
|
152 |
+ where("agents.last_checked_event_id IS NULL OR events.id > agents.last_checked_event_id").to_sql |
|
153 |
+ |
|
154 |
+ agents_to_events = {} |
|
155 |
+ Agent.connection.select_rows(sql).each do |receiver_agent_id, source_agent_id, event_id| |
|
156 |
+ agents_to_events[receiver_agent_id] ||= [] |
|
157 |
+ agents_to_events[receiver_agent_id] << event_id |
|
158 |
+ end |
|
159 |
+ |
|
160 |
+ event_ids = agents_to_events.values.flatten.uniq.compact |
|
161 |
+ |
|
162 |
+ Agent.where(:id => agents_to_events.keys).each do |agent| |
|
163 |
+ agent.update_attribute :last_checked_event_id, event_ids.max |
|
164 |
+ Agent.async_receive(agent.id, agents_to_events[agent.id].uniq) |
|
165 |
+ end |
|
166 |
+ |
|
167 |
+ { |
|
168 |
+ :agent_count => agents_to_events.keys.length, |
|
169 |
+ :event_count => event_ids.length |
|
170 |
+ } |
|
171 |
+ end |
|
175 | 172 |
|
176 |
- Agent.where(:id => agents_to_events.keys).each do |agent| |
|
177 |
- agent.update_attribute :last_checked_event_id, event_ids.max |
|
178 |
- agent.async_receive(agents_to_events[agent.id].uniq) |
|
173 |
+ # Given an Agent id and an array of Event ids, load the Agent, call #receive on it with the Event objects, and then |
|
174 |
+ # save it with an updated _last_receive_at_ timestamp. |
|
175 |
+ # |
|
176 |
+ # This method is tagged with _handle_asynchronously_ and will be delayed and run with delayed_job. It accepts Agent |
|
177 |
+ # and Event ids instead of a literal ActiveRecord models because it is preferable to serialize delayed_jobs with ids. |
|
178 |
+ def async_receive(agent_id, event_ids) |
|
179 |
+ agent = Agent.find(agent_id) |
|
180 |
+ agent.receive(Event.where(:id => event_ids)) |
|
181 |
+ agent.last_receive_at = Time.now |
|
182 |
+ agent.save! |
|
179 | 183 |
end |
184 |
+ handle_asynchronously :async_receive |
|
180 | 185 |
|
181 |
- { |
|
182 |
- :agent_count => agents_to_events.keys.length, |
|
183 |
- :event_count => event_ids.length |
|
184 |
- } |
|
185 |
- end |
|
186 |
+ def run_schedule(schedule) |
|
187 |
+ types = where(:schedule => schedule).group(:type).pluck(:type) |
|
188 |
+ types.each do |type| |
|
189 |
+ type.constantize.bulk_check(schedule) |
|
190 |
+ end |
|
191 |
+ end |
|
186 | 192 |
|
187 |
- def self.run_schedule(schedule) |
|
188 |
- types = where(:schedule => schedule).group(:type).pluck(:type) |
|
189 |
- types.each do |type| |
|
190 |
- type.constantize.bulk_check(schedule) |
|
193 |
+ # You can override this to define a custom bulk_check for your type of Agent. |
|
194 |
+ def bulk_check(schedule) |
|
195 |
+ raise "Call #bulk_check on the appropriate subclass of Agent" if self == Agent |
|
196 |
+ where(:schedule => schedule).pluck("agents.id").each do |agent_id| |
|
197 |
+ async_check(agent_id) |
|
198 |
+ end |
|
191 | 199 |
end |
192 |
- end |
|
193 | 200 |
|
194 |
- # You can override this to define a custom bulk_check for your type of Agent. |
|
195 |
- def self.bulk_check(schedule) |
|
196 |
- raise "Call #bulk_check on the appropriate subclass of Agent" if self == Agent |
|
197 |
- where(:schedule => schedule).find_each do |agent| |
|
198 |
- agent.async_check |
|
201 |
+ # Given an Agent id, load the Agent, call #check on it, and then save it with an updated _last_check_at_ timestamp. |
|
202 |
+ # |
|
203 |
+ # This method is tagged with _handle_asynchronously_ and will be delayed and run with delayed_job. It accepts an Agent |
|
204 |
+ # id instead of a literal Agent because it is preferable to serialize delayed_jobs with ids. |
|
205 |
+ def async_check(agent_id) |
|
206 |
+ agent = Agent.find(agent_id) |
|
207 |
+ agent.check |
|
208 |
+ agent.last_check_at = Time.now |
|
209 |
+ agent.save! |
|
199 | 210 |
end |
211 |
+ handle_asynchronously :async_check |
|
200 | 212 |
end |
201 | 213 |
end |
@@ -8,9 +8,11 @@ describe Agent do |
||
8 | 8 |
end |
9 | 9 |
|
10 | 10 |
it "runs agents with the given schedule" do |
11 |
- mock.any_instance_of(Agents::WeatherAgent).async_check.twice |
|
12 |
- mock.any_instance_of(Agents::WebsiteAgent).async_check.once |
|
11 |
+ weather_agent_ids = [agents(:bob_weather_agent), agents(:jane_weather_agent)].map(&:id) |
|
12 |
+ stub(Agents::WeatherAgent).async_check(anything) {|agent_id| weather_agent_ids.delete(agent_id) } |
|
13 |
+ stub(Agents::WebsiteAgent).async_check(agents(:bob_website_agent).id) |
|
13 | 14 |
Agent.run_schedule("midnight") |
15 |
+ weather_agent_ids.should be_empty |
|
14 | 16 |
end |
15 | 17 |
|
16 | 18 |
it "groups agents by type" do |
@@ -20,7 +22,7 @@ describe Agent do |
||
20 | 22 |
end |
21 | 23 |
|
22 | 24 |
it "only runs agents with the given schedule" do |
23 |
- do_not_allow.any_instance_of(Agents::WebsiteAgent).async_check |
|
25 |
+ do_not_allow(Agents::WebsiteAgent).async_check |
|
24 | 26 |
Agent.run_schedule("blah") |
25 | 27 |
end |
26 | 28 |
end |
@@ -116,19 +118,21 @@ describe Agent do |
||
116 | 118 |
end |
117 | 119 |
end |
118 | 120 |
|
119 |
- describe "#async_check" do |
|
120 |
- it "records last_check_at and calls check" do |
|
121 |
+ describe ".async_check" do |
|
122 |
+ it "records last_check_at and calls check on the given Agent" do |
|
121 | 123 |
@checker = Agents::SomethingSource.new(:name => "something") |
122 | 124 |
@checker.user = users(:bob) |
123 | 125 |
@checker.save! |
124 | 126 |
|
125 |
- @checker.options[:new] = true |
|
126 |
- mock(@checker).check.once |
|
127 |
+ mock(@checker).check.once { |
|
128 |
+ @checker.options[:new] = true |
|
129 |
+ } |
|
127 | 130 |
|
128 |
- @checker.last_check_at.should be_nil |
|
129 |
- @checker.async_check |
|
130 |
- @checker.last_check_at.should be_within(2).of(Time.now) |
|
131 |
+ mock(Agent).find(@checker.id) { @checker } |
|
131 | 132 |
|
133 |
+ @checker.last_check_at.should be_nil |
|
134 |
+ Agents::SomethingSource.async_check(@checker.id) |
|
135 |
+ @checker.reload.last_check_at.should be_within(2).of(Time.now) |
|
132 | 136 |
@checker.reload.options[:new].should be_true # Show that we save options |
133 | 137 |
end |
134 | 138 |
end |
@@ -141,13 +145,13 @@ describe Agent do |
||
141 | 145 |
|
142 | 146 |
it "should use available events" do |
143 | 147 |
mock.any_instance_of(Agents::TriggerAgent).receive(anything).once |
144 |
- agents(:bob_weather_agent).async_check |
|
148 |
+ Agent.async_check(agents(:bob_weather_agent).id) |
|
145 | 149 |
Agent.receive! |
146 | 150 |
end |
147 | 151 |
|
148 | 152 |
it "should track when events have been seen and not see them again" do |
149 | 153 |
mock.any_instance_of(Agents::TriggerAgent).receive(anything).once |
150 |
- agents(:bob_weather_agent).async_check |
|
154 |
+ Agent.async_check(agents(:bob_weather_agent).id) |
|
151 | 155 |
Agent.receive! |
152 | 156 |
Agent.receive! |
153 | 157 |
end |
@@ -161,8 +165,8 @@ describe Agent do |
||
161 | 165 |
mock.any_instance_of(Agents::TriggerAgent).receive(anything).twice { |events| |
162 | 166 |
events.map(&:user).map(&:username).uniq.length.should == 1 |
163 | 167 |
} |
164 |
- agents(:bob_weather_agent).async_check |
|
165 |
- agents(:jane_weather_agent).async_check |
|
168 |
+ Agent.async_check(agents(:bob_weather_agent).id) |
|
169 |
+ Agent.async_check(agents(:jane_weather_agent).id) |
|
166 | 170 |
Agent.receive! |
167 | 171 |
end |
168 | 172 |
end |
@@ -23,20 +23,20 @@ describe Agents::DigestEmailAgent do |
||
23 | 23 |
event2.payload = "Something else you should know about" |
24 | 24 |
event2.save! |
25 | 25 |
|
26 |
- @checker.async_receive([event1.id, event2.id]) |
|
26 |
+ Agents::DigestEmailAgent.async_receive(@checker.id, [event1.id, event2.id]) |
|
27 | 27 |
@checker.reload.memory[:queue].should == ["Something you should know about", "Something else you should know about"] |
28 | 28 |
end |
29 | 29 |
end |
30 | 30 |
|
31 | 31 |
describe "#check" do |
32 | 32 |
it "should send an email" do |
33 |
- @checker.async_check |
|
33 |
+ Agents::DigestEmailAgent.async_check(@checker.id) |
|
34 | 34 |
ActionMailer::Base.deliveries.should == [] |
35 | 35 |
|
36 | 36 |
@checker.memory[:queue] = ["Something you should know about", { :title => "Foo", :url => "http://google.com", :bar => 2 }, { "message" => "hi", :woah => "there" }] |
37 | 37 |
@checker.save! |
38 | 38 |
|
39 |
- @checker.async_check |
|
39 |
+ Agents::DigestEmailAgent.async_check(@checker.id) |
|
40 | 40 |
ActionMailer::Base.deliveries.last.to.should == ["bob@example.com"] |
41 | 41 |
ActionMailer::Base.deliveries.last.subject.should == "something interesting" |
42 | 42 |
get_message_part(ActionMailer::Base.deliveries.last, /plain/).strip.should == "Something you should know about\n\nFoo (bar: 2 and url: http://google.com)\n\nhi (woah: there)" |
@@ -50,7 +50,7 @@ describe Agents::TriggerAgent do |
||
50 | 50 |
@event.save! |
51 | 51 |
|
52 | 52 |
@checker.should_not be_working # no events have ever been received |
53 |
- @checker.async_receive([@event.id]) |
|
53 |
+ Agents::TriggerAgent.async_receive(@checker.id, [@event.id]) |
|
54 | 54 |
@checker.reload.should be_working # no events have ever been received |
55 | 55 |
three_days_from_now = 3.days.from_now |
56 | 56 |
stub(Time).now { three_days_from_now } |